package com.gintama.config;

import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.AsyncConfigurer;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

/**
 *描述：异步配置
 */
@Configuration
@EnableAsync  // 可放在启动类上或单独的配置类
@Slf4j
public class AsyncConfiguration implements AsyncConfigurer {


   public ThreadPoolTaskExecutor  executor() {
       ThreadPoolTaskExecutor taskExecutor  = new ThreadPoolTaskExecutor();
       //核心线程数
       taskExecutor.setCorePoolSize(10);
       //线程池维护线程的最大数量,只有在缓冲队列满了之后才会申请超过核心线程数的线程
       taskExecutor.setMaxPoolSize(100);
       //缓存队列
       taskExecutor.setQueueCapacity(50);
       //设置线程的空闲时间,当超过了核心线程出之外的线程在空闲时间到达之后会被销毁
       taskExecutor.setKeepAliveSeconds(200);
       //异步方法内部线程名称
       taskExecutor.setThreadNamePrefix("async-");
       /**
        * 当线程池的任务缓存队列已满并且线程池中的线程数目达到maximumPoolSize，如果还有任务到来就会采取任务拒绝策略
        * 通常有以下四种策略：
        * ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
        * ThreadPoolExecutor.DiscardPolicy：也是丢弃任务，但是不抛出异常。
        * ThreadPoolExecutor.DiscardOldestPolicy：丢弃队列最前面的任务，然后重新尝试执行任务（重复此过程）
        * ThreadPoolExecutor.CallerRunsPolicy：重试添加当前的任务，自动重复调用 execute() 方法，直到成功
        */
       taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
       taskExecutor.initialize();
       return taskExecutor;
   }

    /**
     * 指定默认线程池
     * The {@link Executor} instance to be used when processing async method invocations.
     */
    @Override
    public Executor getAsyncExecutor() {
        return executor();
    }

    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return (ex, method, params) -> log.error("线程池执行任务发送未知错误, 执行方法：{}", method.getName(), ex);
    }
}
